4

上一篇2018年3月面试心得《跨域问题》

话说我在面试的时候,有那么几天,不知道是中了什么邪,面试的几家公司开始疯狂的问我this
各种的this,绕着弯的问我this,后来我做梦都是this、this、this……你妹的this!

那么先从面试官会怎么问你来说吧。


面试官会问什么样的问题呢

  1. 请说一下作用域和上下文(this)
  2. this会指向哪里
  3. 如何改变this的指向
  4. 出各种绕弯子的题目让你说this是啥

其实这个虽然题目我记得不多了,但是真的问起来还是很恶心的。
因为一旦代码丢出来了,还会涉及到继承构造函数原型链闭包等一系列问题在后面等着你,面试管为了掏你的底细会一问再问,问到你懵逼。

那么我们先来说说第一个问题,作用域和上下文。


什么是作用域?

问得好,我也不知道,待我查一下………………

作用域是在运行时代码中的某些特定部分中变量,函数和对象的可访问性。换句话说,作用域决定了代码区块中变量和其他资源的可见性。

emmmm…………不好理解的话,我说一个比喻吧。
现在我为一个房子专门定一个木窗窗沿,这个窗沿只适合这个房子里面所有需要用的地方,其他房子不能用,那么这个房子就是我木窗的作用域~~~

就像我在

function 房子(){
   var 木窗;
}

我的木窗只属于我的房子,我不可以在房子的外面,例如小区里面直接拿到我的木窗,我必须进入房子去找这个木窗,这是不可以改变的作用域。

然后我们再来看一下

var 景色 = '大海'
function 房子() {
    var 景色 = '大草原';
    this.木窗 = function() {
        console.log(景色)
    }
    木窗()
}
房子()

请问这个时候输出的景色是什么呢~
机智的小朋友肯定会说是大草原~~~
为什么呢~
因为景色我从下往上找啊,找到最近的一张图我就不用继续找了,拿出来用就好啦。

怎么样,惊不惊喜,意不意外,刺不刺激~~~

js中有全局作用域,函数作用域,块级作用域(es6)

  • 全局作用域

很好解释,我所有地方都可以调用的到的,就像路边的广告牌,我们都可以看得到,不需要想办法进谁家里去看。

  • 函数作用域

是产生在函数中的,一个函数内部会出现一块作用域。可以这么理解,函数是我们的房子,我们站在房子外面(全局)的时候,无法看到房子里面的东西。但是我们站在房子里面(函数内部),是可以去透过窗子凑凑整个外面的世界的。

  • 块级作用域

哇塞这个就厉害了,在我们es6里面,新增了一些像let、const之类的语法,可以产生一个块级作用域。
大家应该都做过一个简单的题目,就是一个for循环里面丢一个setTimeout,下面放出代码。

for(var i = 0; i<5; i++) {
    setTimeout(function() {
        console.log(i)
    }, 1000)
}

我们看一下这一个题目,首先会让你回答打印出来的是什么。
机智的小伙伴肯定会回答:5 5 5 5 5
为什么咧,因为这个var的i成了全局的,并不是只在循环里面去使用,setTimeout是一个异步函数,所以我们执行完了for才会去console.log。
这个还涉及到同步异步之类的,会单独开一章来讲,光是基础都已经让人头昏眼花,啊西吧~
那如果我们要让他12345怎么办呢,可以把i保留下来做一个闭包传入,还有一个最方便的办法就是用let来声明他。
ps: 不过一般面试官会跟你说不要用es6,给我一个es5的办法2333333333
=3=


另外说一下let和const和var主要的区别:

  1. 如果你要在块级声明的变量存在全局变量,但是块级作用域内又let或者const了一个局部变量,导致后者绑定这个块级作用域,就会…boom…爆炸,也就是报错~
  2. var大家都知道,进行变量提升,你在第十行声明赋值,我们代码执行的时候其实是会放到最顶部先声明,再执行到第十行的位置进行复制,还没赋值前都是undefind。但是let和const没有哦,如果你提前实用的话就等着报错吧~
  3. const声明一个只读的常量。一旦声明,常量的值就不能改变。但是呢,你可以改变他的属性。

就像

const i = 10;
i = 9;   //  这样是不行的!!!
const u = {a: 'a',b: 'b'}
u.b = 'c' // 这样是可以的

其实很好理解,不过我还是举个?吧……
我const一个大爷,我改变这个大爷的属性,类似给他换个衣服或者换个鞋子,可是大爷还是大爷所以不会报错。
但是我const一个妹子,完事了把妹子改成了大爷,那我不依,报错。


那什么是上下文呢?

这个也好理解,比如我窗户装在客厅,看到的是客厅里面的景色,装到了厨房,就是厨房的景色,当然这个上下文是可以更改的,我可以个这个窗沿里面贴上珠穆拉玛峰的照片,那么这个上下文就被我改了。

function 房子() {
    var 景色 = '大草原';
    this.木窗 = function() {
        console.log(this.景色)
    }
    木窗()
}
房子()

你猜打印出来的是什么~~~
当然是undefind!
想什么呢~不会以为是大草原吧~
为什么咧,因为他的this变了。
下面我把这行代码改为直接打印this,打印出来的是一个window。
为什么?因为我调用房子的地方在window下,我调用了房子,房子调用了木窗,所以this成了window。

那么我们刚刚是不是在房子里面给了一张大草原的画呢,这是不是在房子外面(window层)就找不到了呢,所以是个window。

哈哈哈哈哈
如果你在这里就昏了,那么一定要往下看,不然你永远都搞不清楚this到底会指向哪里。


再说一下第三个问题,改变this的指向。

这个其实也很简单,call或者apply都可以,这个自己看api去~考官会问你两个的区别的,乖宝宝要自己看文档哦~


最后一题,绕着弯子让你说this是啥。

既然你看到了这里,那么正题开始了!!!

到底this指向哪里!!!

问得好,我自己也懵逼。
没关系,我们可以一起捋一捋。
容老夫先沐浴更衣,焚香祈祷你们不要被我带歪了。

首先,跟我一起读一遍下面的两句话(当然,如果有补充可以留言):

1: 谁调用指向谁,没有谁就是window

2: 除了call、apply、bind和箭头函数

首先我们在全局下打印一下this,控制台输出的是window,这个没有疑问吧~

我们再来通过函数调用一下

function 房子() {
    this.木窗 = function() {
        console.log(this)
    }
    木窗()
}
房子()

那么我们再回过头去看刚刚的房子窗子。
我们是哪里开始调用房子的?
当然是window,其实房子()就等同于window.房子()
所以呢,调用房子的是window,那么房子最底下会调用木窗,所以真正的调用者被抓出来了。
window => 房子 => 木窗
函数的调用者就是this,请抓住始作俑者,这道题就是window这个小婊渣,就是他~

那么我们再看下一个。
通过对象的属性来调用:

var obj = {

    say: function() {

        console.log(this);

    }

};

obj.say();

打印的结果是: {say: ƒ}
谁调用就指向谁,这个obj.say()的执行方法调用者是前面的obj,所以当前的这个this指向了obj

再看一个

var obj = {

    say: function() {
    
        var hehe = function() {
        
            console.log(this)
        }

        hehe()

    }

};

obj.say();

这个this指向了window
为什么?因为这个hehe并不是obj上面的属性,没有找到调用者是谁,所以默认指向window。


下面我们来看一下其余几个指向谁。
call和apply大家都知道,可以改变this的指向。

var obj = {a: 'a'};
function b() {
console.log(this)
}
b.apply(obj)

打印结果:{a: "a"}

call差不多,两者差别只是参数传的不一样,一个可以传数组。
具体情况,宝宝们文档走一波~

然后再来看一下bind
哇塞这个也厉害了
直接上代码

var axiba = {hehe: 'hehe'}
var obj = {
a: 'a',
b:function() {
    console.log(this)
}.bind(axiba)
};
b()

// 或者

var axiba = {hehe: 'hehe'}
var obj = {
a: 'a',
b:function() {
    console.log(this)
}
};
var u = obj.b.bind(axiba)
u()

执行的是神马~
当然是指向axiba~

最后一个,就是es6的箭头函数


我要单独为箭头函数加个粗

官网逛一逛,发现了几句话。

箭头函数有几个使用注意点。

(1)函数体内的this对象,就是定义时所在的对象,而不是使用时所在的对象。

(2)不可以当作构造函数,也就是说,不可以使用new命令,否则会抛出一个错误。

(3)不可以使用arguments对象,该对象在函数体内不存在。如果要用,可以用 rest 参数代替。

(4)不可以使用yield命令,因此箭头函数不能用作 Generator 函数。

上面四点中,第一点尤其值得注意。this对象的指向是可变的,但是在箭头函数中,它是固定的。

要理解其实也很容易,首先……
上代码……

function foo() {
  setTimeout(() => {
    console.log('id:', this.id);
  }, 100);
}

var id = 21;

foo.call({ id: 42 });
代码中,setTimeout的参数是一个箭头函数,这个箭头函数的定义生效是在foo函数生成时,而它的真正执行要等到 100 毫秒后。如果是普通函数,执行时this应该指向全局对象window,这时应该输出21。但是,箭头函数导致this总是指向函数定义生效时所在的对象(本例是{id: 42}),所以输出的是42。

所以以后有人问你es6的箭头函数的this和es5函数中的this的区别,那么就是es6不可改变,始终指向定义的对象,es5是根据调用环境的,如果没有调用者,默认window。

因为考官会出得题目千千万,题海战术几乎无用,我之前也是作用域和上下文傻傻分不清,虽然现在也懵懂阶段,不过学无止尽嘛。
有什么表达不当的地方,悄悄告诉我哦~
么么哒


darklinda588
141 声望6 粉丝